Переменные

Переменные используются для хранения значений (sic!). Переменная характеризуется типом и именем. Начнём с имени. В си переменная может начинаться с подчерка или буквы, но не с числа. Переменна может включать в себя символы английского алфавита, цифры и знак подчёркивания. Переменная не должна совпадать с ключевыми словами (это специальные слова, которые используются в качестве управляющих конструкций, для определения типов и т.п.)

auto double int struct
break else long switch
register typedef char extern
return void case float
unsigned default for signed
union do if sizeof
volatile continue enum short
while inline

А также ряд других слов, специфичных для данной версии компилятора, например far, near, tiny, huge, asm, asm и пр.

Например, правильные идентификаторы a, _, _1_, Sarkasm, a_long_variable, aLongVariable, var19, defaultX, char_type

неверные

1a, $value, a-long-value, short

Си - регистрозависимый язык. Переменные с именами a и A, или end и END, или perfectDark и PerfectDarK – это различные переменные.

Типы переменных

Тип переменной определяет

  1. Размер переменной в байтах (сколько байт памяти выделит компьютер для хранения значения)
  2. Представление переменной в памяти (как в двоичном виде будут расположены биты в выделенной области памяти).

В си несколько основных типов. Разделим их на две группы - целые и числа с плавающей точкой.

Целые

  • char - размер 1 байт. Всегда! Это нужно запомнить.
  • short - размер 2 байта
  • int - размер 4 байта
  • long - размер 4 байта
  • long long - размер 8 байт.

Здесь следует сделать замечание. Размер переменных в си не определён явно, как размер в байтах. В стандарте только указано, что

char <= short <= int <= long <= long long

Указанные выше значения характерны для компилятора VC2012 на 32-разрядной машине. Так что, если ваша программа зависит от размера переменной, не поленитесь узнать её размер.

Теперь давайте определим максимальное и минимальное число, которое может хранить переменная каждого из типов. Числа могут быть как положительными, так и отрицательными. Отрицательные числа используют один бит для хранения знака. Иногда знак необходим (например, храним счёт в банке, температуру, координату и т.д.), а иногда в нём нет необходимости (вес, размер массива, возраст человека и т.д.). Для этого в си используется модификатор типа signed и unsigned.

unsigned char - все 8 бит под число, итого имеем набор чисел от 00000000 до 11111111 в двоичном виде, то есть от 0 до 255 signed char от -128 до 128.

В си переменные по умолчанию со знаком. Поэтому запись char и signed char эквивалентны.

Тип Размер, байт Минимальное значение Максимальное значение
unsigned char 1 0 255
signed char
( char )
1 -128 127
unsigned short 2 0 65535
signed short
( short )
2 -32768 32767
unsigned int
( unsigned )
4 0 4294967296
signed int
( int )
4 -2147483648 2147483647
unsigned long 4 0 4294967296
signed long
( long )
4 -2147483648 2147483647
unsigned long long 8 0 18446744073709551615
signed long long
( long long )
8 -9223372036854775808 9223372036854775807

sizeof

В си есть оператор, который позволяет получить размер переменной в байтах.

sizeof переменная, или sizeof(переменная) или sizeof(тип). Это именно оператор, потому что функция не имеет возможности получить информацию о размере типов во время выполнения приложения.

Напишем небольшую программу чтобы удостовериться в размерах переменных.

#include <conio.h>
#include <stdio.h>

int main() {
    char c;
    short s;
    int i;
    long l;
    long long L;

    //Вызов sizeof как "функции"
    printf("sizeof(char)  = %d\n", sizeof(c));
    printf("sizeof(short) = %d\n", sizeof(s));
    printf("sizeof(int)   = %d\n", sizeof(i));
    printf("sizeof(long)  = %d\n", sizeof(l));
    printf("sizeof(long long) = %d\n", sizeof(L));

    //Вызов как оператора
    printf("sizeof(char)  = %d\n", sizeof c);
    printf("sizeof(short) = %d\n", sizeof s);
    printf("sizeof(int)   = %d\n", sizeof i);
    printf("sizeof(long)  = %d\n", sizeof l);
    printf("sizeof(long long) = %d\n", sizeof L);

    _getch();
}

(Я думаю ясно, что переменные могут иметь любое валидное имя). Эту программу можно было написать и проще

#include <conio.h>
#include <stdio.h>

int main() {

    printf("sizeof(char)  = %d\n", sizeof(char));
    printf("sizeof(short) = %d\n", sizeof(short));
    printf("sizeof(int)   = %d\n", sizeof(int));
    printf("sizeof(long)  = %d\n", sizeof(long));
    printf("sizeof(long long) = %d\n", sizeof(long long));
    //нельзя произвести вызов sizeof как оператора для имени типа
    //sizeof int - ошибка компиляции

    _getch();
}

В си один и тот же тип может иметь несколько названий

short === short int

long === long int

long long === long long int

unsigned int === unsigned

Типы с плавающей точкой

  • float - 4 байта
  • long float - 8 байт
  • double - 8 байт
  • long double - 8 байт

Здесь также приведены значения для VC2012 (x86), по стандарту размер типов

float <= long float <= double <= long double

все числа с плавающей точкой - со знаком.

Тип Размер, байт Количество значащих знаков мантиссы Минимальное значение Максимальное значение
float 4 6-7 1.175494351 E – 38 3.402823466 E + 38
double 8 15-16 2.2250738585072014 E – 308 1.7976931348623158 E + 308

Переполнение переменных

Си не следит за переполнением переменных. Это значит, что постоянно увеличивая значение, скажем, переменной типа int в конце концов мы "сбросим значение"

#include <conio.h>
#include <stdio.h>

void main() {
    unsigned a = 4294967295;
    int b = 2147483647;
    //Переполнение беззнакового типа
    printf("%u\n", a);
    a += 1;
    printf("%u", a);
    //Переполнение знакового типа
    printf("%d\n", b);
    b += 1;
    printf("%d", b);
    getch();
}

Вообще, поведение при переполнении переменной определено только для типа unsigned: беззнаковое целое сбросит значение.

Для остальных типов может произойти что угодно, и если вам необходимо следить за переполнением, делайте это вручную, проверяя аргументы, либо используйте иные способы, зависящие от компилятора и архитектуры процессора.

Постфиксное обозначение типа

При работе с числами можно с помощью литер в конце числа явно указывать его тип, например

  • 11 - число типа int
  • 10u - unsigned
  • 22l или 22L - long
  • 3890ll или 3890LL - long long (а также lL или Ll)
  • 80.0f или 80.f или 80.0F - float (обязательно наличие десятичной точки в записи)
  • 3.0 - число типа double

Экспоненциальная форма записи также по умолчанию обозначает число типа double.

#include <conio.h>
#include <stdio.h>

int main() {

    printf("sizeof(int) = %d\n", sizeof(10));
    printf("sizeof(unigned) = %d\n", sizeof(10u));
    printf("sizeof(long) = %d\n", sizeof(10l));
    printf("sizeof(long long) = %d\n", sizeof(10ll));
    printf("sizeof(float) = %d\n", sizeof(10.f));
    printf("sizeof(double) = %d\n", sizeof(10.));
    printf("sizeof(double) = %d\n", sizeof(10e2));

    _getch();
}

Следующий код, однако, не будет приводить к ошибкам, потому что происходит неявное преобразование типа

int a = 10u;
double g = 3.f;

Шестнадцатеричный и восьмеричный формат

Во время работы с числами можно использовать шестнадцатеричный и восьмеричный формат представления. Числа в шестнадцатиричной системе счисления начинаются с 0x, в восьмеричной системе с нуля. Соответственное, если число начинается с нуля, то в нём не должно быть цифр выше 7:

#include <conio.h>
#include <stdio.h>

void main() {
    int x = 0xFF;
    int y = 077;
    printf("hex x = %x\n", x);
    printf("dec x = %d\n", x);
    printf("oct x = %o\n", x);
    printf("oct y = %o\n", y);
    printf("dec y = %d\n", y);
    printf("hex y = %x", y);
    _getch();
}

Экспоненциальная форма представления чисел

Экспоненциальной формой представления числа называют представление числа в виде

M e ± p

, где M - мантиса числа, p - степень десяти. При этом у мантисы должен быть один ненулевой знак перед десятичной запятой.

Например 1.25 === 1.25e0, 123.5 === 1.235e2, 0.0002341 === 2.341e-4 и т.д.

Представления 3.2435e7 эквивалентно 3.2435e+7

Существеут и другое представление ("инженерное"), в котором степень должна быть кратной тройке.

Например 1.25 === 1.25e0, 123.5 === 123.5e0, 0.0002341 === 234.1e-6, 0.25873256 === 258.73256e-3 и т.д.

Объявление переменных

В си переменные объявляются всегда в начале блока (блок - участок кода ,ограниченный фигурными скобками)

<возвращаемый тип> <имя функции> (<тип> <аргумент> [, <тип> <аргумент>]) {
    объявление переменных

    всё остальное
}

При объявлении переменной пишется её тип и имя.

int a;
double parameter;

Можно объявить несколько переменных одного типа, разделив имена запятой

long long arg1, arg2, arg3;

Например

#include <stdio.h&>
#include <conio.h>

int main() {
    int a = 10;
    int b;
    while (a>0){
        int z = a*a;
        b += z;
    }
}

Здесь объявлены переменные a и b внутри функции main, и переменная z внутри тела цикла.

Следующий код вызовет ошибку компиляции

int main() {

    int i;
    i = 10;
    int j;
}

Это связано с тем, что объявление переменной стоит после оператора присваивания.

При объявлении переменных можно их сразу инициализировать.

int i = 0;<br/>

При этом инициализация при объявлении переменной не считается за отдельный оператор, поэтому следующий код будет работать

int main() {
    int i = 10;
    int j;
}

Начальное значение переменной

Очень важно запомнить, что переменные в си не инициализируются по умолчанию нулями, как во многих других языках программирования. После объявления переменной в ней хранится "мусор" - случайное значение, которое осталось в той области памяти, которая была выделена под переменную.

Это связано, в первую очередь, с оптимизацией работы программы: если нет необходимости в инициализации, то незачем тратить ресурсы для записи нулей.

#include <conio.h>
#include <stdio.h>

int main() {
    int i;
    printf("%d", i);
    getch();
}

Если выполнять эту программу на VC, то во время выполнения вылетит предупреждение

Run-Time Check Failure #3 - The variable 'i' is being used without being initialized.

Если нажать "Продолжить", то программа выведет "мусор". В многих других компиляторах при выполнении программы не будет предупреждения.

Область видимости переменной

Переменные бываю локальными (объявленными внутри какой-нибудь функции) и глобальными. Глобальная переменная видна всем функциям, объявленным в данном файле.

Локальная переменная ограничена своей областью видимости. Когда я говорю, что переменная "видна в каком-то месте", это означает, что в этом месте она определена и её можно использовать.

Например, рассмотрим программу, в которой есть глобальная переменная

#include <conio.h>
#include <stdio.h>

int global = 100;

void foo() {
    printf("foo: %d\n", global);
}

void bar(int global) {
    printf("bar: %d\n", global);
}

int main() {
    foo();
    bar(333);
    getch();
}

Будет выведено

foo: 100
bar: 333

Здесь глобальная переменная global видна всем функциям. Но аргумент функции затирает глобальную переменную, поэтому при передаче аргумента 333 выводится локальное значение 333.

Вот другой пример

#include <conio.h>
#include <stdio.h>

int global = 100;

int main() {
    int global = 555;
    printf("%d\n", global);
    getch();
}

Программа выведет 555. Также, как и в прошлом случае, локальная переменная "важнее".

Переменная, объявленная в некоторой области видимости не видна вне её, например

#include <conio.h>
#include <stdio.h>

int global = 100;

int main() {
    int x = 10;
    {
        int y = 30;
        printf("%d", x);
    }
    printf("%d", y);
}

Этот пример не скомпилируется, потому что переменная y существует только внутри своего блока.

Вот ещё пример, когда переменные, объявленные внутри блока перекрывают друг друга

#include <conio.h>
#include <stdio.h>

int global = 100;

int main() {
    int x = 10;
    {
        int x = 20;
        {
            int x = 30;
            printf("%d\n", x);
        }
        printf("%d\n", x);
    }
    printf("%d\n", x);
    getch();
}

Программа выведет

30
20
10

Глобальных переменных необходимо избегать. Объяснение почему оставляю на совести вашего опыта.

Переменные могут быть не только целочисленными и с плавающей точкой. Существует множество других типов, которые мы будем изучать в дальнейшем.